6.1 单关系数据查询 · 上机练习(续)

聚合 · 分组 · 排序 · LIMIT

延续上节课的 teaching 数据库,继续练习聚合函数、GROUP BY 分组、ORDER BY 排序和 LIMIT 限制结果数量。

4聚合函数
5分组查询
6排序
7LIMIT
前置要求:本练习使用上节课创建的 teaching 数据库及其 5 张表(c、s、t、sc、tc)。
如果你是新环境,请先运行上一份练习的"阶段一 + 阶段二"完成建库建表,然后回到这里继续。
如果数据库已存在,只需执行 USE teaching; 即可开始。
0
确认环境 — USE teaching
准备
SQL
USE teaching; -- 确认 5 张表都在 SHOW TABLES;
✅ 应看到 c、s、sc、t、tc 五张表。如果缺失请先补建。
Part 04

聚合函数查询

COUNT · SUM · AVG · MAX · MIN — 对一组值进行统计计算

1
例 6-20:SUM 和 AVG — 求总分与平均分
练习

目标:查询学号"s2"的总分和平均分。先不加别名运行看字段名,再加别名。

SQL — 不加别名
SELECT SUM(score), AVG(score) FROM sc WHERE sno = 's2';
SQL — 加别名(推荐)
SELECT SUM(score) AS 总分, AVG(score) AS 平均分 FROM sc WHERE sno = 's2';
✓ 期望结果
总分平均分
225.0075.000000
⚠ s2 选了 4 门课,其中 c5 成绩为 NULL。SUM 和 AVG 只计算了 3 门有效成绩,NULL 不参与!平均分 = 225 ÷ 3 = 75,不是 225 ÷ 4。
2
例 6-21:MAX 和 MIN — 最高/最低课时
练习
SQL
SELECT MAX(ct) AS 最高课时, MIN(ct) AS 最低课时, MAX(ct) - MIN(ct) AS 最大课时差 FROM c;
✓ 期望结果
最高课时最低课时最大课时差
643232
💡 聚合函数可以参与算术运算,如 MAX(ct) - MIN(ct)。
3
例 6-22:COUNT — 统计选课门数(NULL 陷阱)
练习

目标:分别用 COUNT(cno) 和 COUNT(score) 查询 s2 的选课门数,观察差异。

SQL — COUNT(cno)(正确)
SELECT sno, COUNT(cno) AS 选课门数 FROM sc WHERE sno = 's2';
SQL — COUNT(score)(有坑!)
-- 试试看,结果和上面一样吗? SELECT sno, COUNT(score) AS 选课门数 FROM sc WHERE sno = 's2';
✓ 期望对比
写法选课门数说明
COUNT(cno)4✓ 正确
COUNT(score)3✗ 少了 1(NULL 不被计数)
核心教训:COUNT(字段名) 只计算非 NULL 值。如果该字段可能有 NULL,用 COUNT(*) 或 COUNT(其他非空字段) 更安全。
4
补充练习:s1 的选课门数(无 NULL 情况)
练习

再查 s1 的选课门数,验证无 NULL 时 COUNT(cno) 和 COUNT(score) 结果一致。

SQL
SELECT sno, COUNT(cno) AS 选课门数 FROM sc WHERE sno = 's1';
✓ 期望结果
sno选课门数
s13
5
例 6-23:COUNT(DISTINCT) — 统计不重复值
练习

目标:查询学生表中有多少种不同的专业。先不加 DISTINCT 看效果,再加上。

SQL — 不加 DISTINCT
-- 不去重,会把重复专业也数进去 SELECT COUNT(maj) AS 专业数量 FROM s;
SQL — 加 DISTINCT(正确)
SELECT COUNT(DISTINCT maj) AS 专业数量 FROM s;
✓ 期望对比
写法专业数量
COUNT(maj)8
COUNT(DISTINCT maj)4
💡 8 名学生只有 4 种专业(计算机、信息、自动化、数学)。DISTINCT 先去重再计数。
6
例 6-24:COUNT(*) — 统计元组数量
练习
SQL
SELECT dept, COUNT(*) AS 教师数量 FROM t WHERE dept = '信息学院';
✓ 期望结果
dept教师数量
信息学院5
💡 COUNT(*) 统计行数,不看具体字段值,也不跳过 NULL 行。
Part 05

分组查询

GROUP BY 分组 + HAVING 对组筛选 — 理解 WHERE 与 HAVING 的区别

7
例 6-25:GROUP BY — 每门课程的选课人数
练习
SQL
SELECT cno AS 课程号, COUNT(*) AS 选课人数 FROM sc GROUP BY cno;
✓ 期望结果
课程号选课人数
c14
c23
c32
c43
c51
c61
c72
💡 GROUP BY cno 把 cno 相同的行归为一组,COUNT(*) 分别统计每组的行数。
8
例 6-26:HAVING — 选修 ≥ 3 门课的学生
练习

目标:先按学号分组统计选课门数,再用 HAVING 筛选出 ≥ 3 门的。

SQL — 第一步:先看所有人的选课门数
-- 先不加 HAVING,看全貌 SELECT sno AS 学号, COUNT(*) AS 选课门数 FROM sc GROUP BY sno;
SQL — 第二步:加 HAVING 筛选
SELECT sno AS 学号, COUNT(*) AS 选课门数 FROM sc GROUP BY sno HAVING COUNT(*) >= 3;
✓ 加 HAVING 后的期望结果
学号选课门数
s13
s24
9
深入理解:WHERE 与 HAVING 的区别(实验对比)
思考

WHERE

在分组之前筛选

不能使用聚合函数

HAVING

在分组之后筛选

可以使用聚合函数

实验:查询"成绩不为空的选课记录中",选课 ≥ 2 门的学生。体会 WHERE 在 GROUP BY 前执行。

SQL — WHERE + GROUP BY + HAVING 联用
SELECT sno AS 学号, COUNT(*) AS 有效选课数 FROM sc WHERE score IS NOT NULL -- ① 先过滤掉 NULL 行 GROUP BY sno -- ② 再按学号分组 HAVING COUNT(*) >= 2; -- ③ 最后筛选组
🔑 执行顺序:WHERE → GROUP BY → HAVING。WHERE 先排除了 NULL 成绩的行,然后才分组和过滤。
Part 06

查询结果排序

ORDER BY 子句 — ASC 升序(默认)· DESC 降序 · 多字段排序

10
例 6-27:单字段降序 — 按成绩排序
练习
SQL
SELECT sno, cno, score FROM sc WHERE sno = 's2' ORDER BY score DESC;
✓ 期望结果(注意 NULL 排在最后或最前取决于实现)
snocnoscore
s2c282.00
s2c178.00
s2c765.00
s2c5NULL
💡 MySQL 中 DESC 排序时,NULL 被视为最小值,排在最后。
11
例 6-28:观察默认排序规则
练习

目标:按课时降序查课程表,观察课时相同时 MySQL 默认怎么排。

SQL
SELECT * FROM c ORDER BY ct DESC;
✓ 期望结果(注意 ct=48 和 ct=32 的行的排列)
cnocnct
c4数据结构64
c7高等数学60
c5数据库系统56
c2程序设计基础48
c3线性代数48
c1Java程序设计40
c6数据挖掘32
c8控制理论32
💡 ct=48 的两门课按 cno 升序排列(c2 在 c3 前),ct=32 同理。这是 MySQL 默认按主键升序补充排序。
12
例 6-29:多字段排序 — 主排序 + 次排序
练习

目标:课时降序 → 课时相同按课程名降序。对比上一步结果的差异。

SQL
SELECT * FROM c ORDER BY ct DESC, cn DESC;
✓ 期望结果(注意 ct=48 时 c3 线性代数 在 c2 前)
cnocnct
c4数据结构64
c7高等数学60
c5数据库系统56
c3线性代数48
c2程序设计基础48
c1Java程序设计40
c8控制理论32
c6数据挖掘32
⚠ 对比上一步:ct=48 时,上一步 c2 在 c3 前(默认按主键),这一步 c3 在 c2 前(按课程名 cn 降序排列,"线" > "程" 字典序)。
Part 07

限制查询结果数量(LIMIT)

LIMIT 子句限制返回行数,OFFSET 指定起始偏移 — 分页的基础

13
例 6-30:LIMIT + OFFSET — 两种等价写法
练习

目标:从第 2 位教师开始取 3 位。分别用两种语法,验证结果相同。

SQL — 写法一:LIMIT offset, count
SELECT tno, tn, prof FROM t LIMIT 1, 3;
SQL — 写法二:LIMIT count OFFSET offset
SELECT tno, tn, prof FROM t LIMIT 3 OFFSET 1;
✓ 期望结果(两种写法完全相同)
tnotnprof
t2周建讲师
t3顾伟副教授
t4赵礼教授
💡 LIMIT 1, 3 — 第一个数字 1 是偏移量(跳过 1 行),第二个数字 3 是取几行。OFFSET 从 0 开始计数。
14
拓展实验:模拟分页(每页 3 条)
思考

场景:课程表 8 行,每页显示 3 行。分别查第 1、2、3 页。

SQL — 第 1 页
SELECT * FROM c LIMIT 3 OFFSET 0; -- 第1~3行
SQL — 第 2 页
SELECT * FROM c LIMIT 3 OFFSET 3; -- 第4~6行
SQL — 第 3 页
SELECT * FROM c LIMIT 3 OFFSET 6; -- 第7~8行(只剩2行)
🔑 分页公式:OFFSET = (页码 - 1) × 每页行数。第 3 页只返回 2 行,因为总共只有 8 行。
15
例 6-31:综合应用 — GROUP BY + ORDER BY + LIMIT
练习

目标:查询每门课的选课人数,按人数降序,只显示前 3 名。

SQL
SELECT cno AS 课程号, COUNT(*) AS 选课人数 FROM sc GROUP BY cno ORDER BY 选课人数 DESC LIMIT 3;
✓ 期望结果(选课人数最多的 3 门课)
课程号选课人数
c14
c23
c43
✅ 这是一个典型的"Top N"查询模式:GROUP BY 分组 → ORDER BY 排序 → LIMIT 取前 N。在实际工作中非常常用!
自测挑战

独立完成 — 先写再看答案

综合运用聚合函数、GROUP BY、ORDER BY、LIMIT

🏋️ 挑战 1:查询选课表 sc 中的最高分、最低分和平均分。
参考答案
SELECT MAX(score) AS 最高分, MIN(score) AS 最低分, AVG(score) AS 平均分 FROM sc;
🏋️ 挑战 2:查询每个学生的平均成绩,按平均成绩降序排列。(提示:GROUP BY + ORDER BY)
参考答案
SELECT sno, AVG(score) AS 平均成绩 FROM sc GROUP BY sno ORDER BY 平均成绩 DESC;
🏋️ 挑战 3:查询平均成绩 ≥ 80 分的学生学号和平均成绩。(提示:HAVING)
参考答案
SELECT sno, AVG(score) AS 平均成绩 FROM sc GROUP BY sno HAVING AVG(score) >= 80;
🏋️ 挑战 4:查询每个学院的教师人数,只显示教师人数 ≥ 2 的学院,结果按人数降序。
参考答案
SELECT dept AS 学院, COUNT(*) AS 教师人数 FROM t GROUP BY dept HAVING COUNT(*) >= 2 ORDER BY 教师人数 DESC;
🏋️ 挑战 5:查询选课人数最多的那一门课的课程号和选课人数。(提示:ORDER BY + LIMIT 1)
参考答案
SELECT cno AS 课程号, COUNT(*) AS 选课人数 FROM sc GROUP BY cno ORDER BY 选课人数 DESC LIMIT 1;
🏋️ 挑战 6:查询所有学生的选课信息(学号、课程号、成绩),按学号升序排列,学号相同再按成绩降序,只显示前 5 条。
参考答案
SELECT sno, cno, score FROM sc ORDER BY sno ASC, score DESC LIMIT 5;
🏋️ 挑战 7(综合):查询"有成绩的选课记录中",每个学生的总分,只显示总分排名前 3 的学生学号和总分。
参考答案
SELECT sno, SUM(score) AS 总分 FROM sc WHERE score IS NOT NULL GROUP BY sno ORDER BY 总分 DESC LIMIT 3;
🔑 本题同时用到了 WHERE + GROUP BY + ORDER BY + LIMIT,是 SELECT 语句所有子句的综合运用。子句顺序不可颠倒!
🎉

6.1 节全部练习完成!

你已掌握了单关系数据查询的 7 大模块:
查询结构 → 无条件查询 → 条件查询 → 聚合函数 → 分组查询 → 排序 → LIMIT

记住 SELECT 语句的执行顺序:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT